#include <stdlib.h>
#include <stdio.h>
#include <sys/time.h>
#include <time.h>
#include <termios.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include <setjmp.h>
#include <signal.h>

#define CLEAR() printf("\033[2J")
#define MOVEUP(x) printf("\033[%dA", (x))
#define MOVEDOWN(x) printf("\033[%dB", (x))
#define MOVELEFT(y) printf("\033[%dD", (y))
#define MOVERIGHT(y) printf("\033[%dC", (y))
#define MOVETO(x, y) printf("\033[%d;%dH", (x), (y))
#define RESET_CURSOR() printf("\033[H")
#define HIDE_CURSOR() printf("\033[?25l")
#define SHOW_CURSOR() printf("\033[?25h")
#define HIGHT_LIGHT() printf("\033[7m")
#define UN_HIGHT_LIGHT() printf("\033[27m")
#define NEWLINE() printf("\n")
#define XLEN 20
#define YLEN 20

static const int shape[7][4][18] = {
                              {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1,     2, 1},   //        
                               {0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0,     1, 2},   //   []   []    [][][]     []
                               {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0,     2, 1},   // [][][] [][]    []     [][]
                               {0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1,     1, 2}},  //        []               []

                              {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1,     2, 1},   //
                               {0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1,     1, 2},   //         []            [][]
                               {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0,     2, 1},   //      [] []    [][][]    []
                               {0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1,     1, 2}},  //  [][][] [][]  []        []

                              {{0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1,     1, 2},   //
                               {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1,     2, 1},   //          [][]            []
                               {0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0,     1, 2},   // []       []    [][][]    []
                               {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1,     2, 1}},  // [][][]   []        []  [][]

                              {{0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1,     1, 2},   // 
                               {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0,     2, 1},   //    []
                               {0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1,     1, 2},   //    [][]     [][]
                               {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0,     2, 1}},  //      []   [][]

                              {{0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0,     1, 2},   //
                               {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1,     2, 1},   //      []
                               {0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0,     1, 2},   //    [][]   [][]
                               {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1,     2, 1}},  //    []       [][]

                              {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1,     2, 2},   //
                               {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1,     2, 2},   //
                               {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1,     2, 2},   //     [][]
                               {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1,     2, 2}},  //     [][]

                              {{0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1,     0, 3},   //     []
                               {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,     3, 0},   //     []     [][][][]
                               {0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1,     0, 3},   //     [] 
                               {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,     3, 0}}   //     []     
                             };

typedef struct _block
{
    int startX;
    int startY;
    int score;
    int level;
    int num;
    int mode;
    int num_next;
    int mode_next;
    int left;
    int top;
    int preleft;
    int pretop;
    int eraseflag;
    const int *shape;
    const int *next;
    int box[XLEN][YLEN];
    struct itimerval speed;
    jmp_buf env;
} block;

static block *b;
void block_init();
void block_destroy();
void print_UI();
void print_score();
void print_level();
void print_help();
void move_down();
void move_right();
void move_left();
void print_shape();
void print_next();
void key_control();
void change_shape();
void erase_preshape();
int  getRand(int num);

void print_UI()
{
    int x, y;
    printf("\33[41m");
    MOVETO(b->startX, b->startY);
    for (x = 0; x < XLEN + 10 * 2; x++)
        printf("=");
    y = b->startY;
    for (x = b->startX + 1; x <= b->startX + YLEN; x++)
    {
        MOVETO(x, y);
        printf("||");
    }
    y = b->startY + XLEN + 2;
    for (x = b->startX + 1; x <= b->startX + YLEN; x++)
    {
        MOVETO(x, y);
        printf("||");
    }
    y = b->startY + XLEN + 18;
    for (x = b->startX + 1; x <= b->startX + YLEN; x++)
    {
        MOVETO(x, y);
        printf("||");
    }
    MOVETO(b->startX + 8, b->startY + 24);
    for (x = 0; x < 7; x++)
        printf("==");
    MOVETO(b->startX + YLEN + 1, b->startY);
    for (y = 0; y < XLEN + 10 * 2; y++)
        printf("=");
    printf("\33[0m");
    print_score();
    print_level();
    print_help();
    print_shape();
    print_next();
    fflush(stdout);
}

void erase_preshape()
{
    int i, row, col, line = 0;
    for (i = 0; i < 16; i++)
    {
        row = i / 4;
        col = i % 4;
        if (row >= b->shape[16])
        {
            if (col == 0)
            { 
                MOVETO(b->pretop + line, b->preleft);
                line++;
            }
            if (col >= b->shape[17])
            {
                if (b->shape[i] == 1)
                    printf("  ");
                else
                    MOVERIGHT(2);
            }
        }
    }
}

void print_shape()
{
    int i, row, col, line = 0;
    if (b->eraseflag == 0)
    {
        erase_preshape();
    }
    for (i = 0; i < 16; i++)
    {
        row = i / 4;
        col = i % 4;
        if (row >= b->shape[16])
        {
            if (col == 0)
            { 
                MOVETO(b->top + line, b->left);
                line++;
            }
            if (col >= b->shape[17])
            {
                if (b->shape[i] == 1)
                    printf("\33[45m[]\33[0m");
                else
                    MOVERIGHT(2);
            }
        }
    }
    b->eraseflag = 0;
    fflush(stdout);
}

void print_next()
{
    int i, j;
    for (i = 0; i < 4; i++)
    {        
        MOVETO(b->startX + 2 + i, YLEN + b->startY + 5);
        for (j = 0; j < 4; j++)
        {
            if((b->next[i * 4 + j]) == 1)
                printf("\33[45m[]\33[0m");
            else
                printf("  ");
        }
        fflush(stdout);
    }
}

void print_score()
{
    MOVETO(b->startX + 12, b->startY + XLEN + 6);
    printf("\033[32mSCORE:\33[0m");
    MOVETO(b->startX + 13, b->startY + XLEN + 8);
    printf("\033[32m%d\33[0m", b->score);
}

void print_level()
{
    MOVETO(b->startX + 10, b->startY + XLEN + 6);
    printf("\033[32mLEVEL: %d\33[0m", b->level);
}

void print_help()
{
    printf("\033[36m");
    MOVETO(b->startX + 15, b->startY + XLEN + 5);
    printf("space:  stop");
    MOVETO(b->startX + 16, b->startY + XLEN + 5);
    printf("↑ :   change");
    MOVETO(b->startX + 17, b->startY + XLEN + 5);
    printf("← : moveleft");
    MOVETO(b->startX + 18, b->startY + XLEN + 5);
    printf("→ :moveright");
    MOVETO(b->startX + 19, b->startY + XLEN + 5);
    printf("↓ : falldown");
    printf("\33[0m");
}

void block_init()
{
    CLEAR();
    HIDE_CURSOR();
    srand(time(NULL));
    b = (block*)malloc(sizeof(struct _block));
    if(b == NULL) return;

    b->startX = 3;
    b->startY = 5;
    b->level = 1;
    b->score = 0;
    b->eraseflag = 1;
    memset(b->box, 0, sizeof(b->box));
    b->speed.it_value.tv_sec = 0;
    b->speed.it_value.tv_usec = 800000;
    b->speed.it_interval.tv_sec = 0;
    b->speed.it_interval.tv_usec = 800000;

    b->num = getRand(7);
    b->mode = getRand(4);
    b->shape = shape[b->num][b->mode];
    b->left = b->startY + 8;
    b->top = b->startX + 1;
    b->num_next = getRand(7);
    b->mode_next = getRand(4);
    b->next = shape[b->num_next][b->mode_next];
}

void block_destroy()
{
    SHOW_CURSOR();
    if (b != NULL)
    {
        free(b);
    }
}

int getRand(int num)
{
    if (num <= 0) return 0;
    return rand() % num;
}

void move_down()
{
    if (b->top - b->startX - b->shape[16] + 3 >= XLEN)
    {
        b->shape = b->next;
        b->num = b->num_next;
        b->mode = b->mode_next;
        b->top = b->startX + 1;
        b->left = b->startY + 8;

        b->num_next = getRand(7);
        b->mode_next = getRand(4);
        b->next = shape[b->num_next][b->mode_next];
        b->eraseflag = 1;
        print_shape();
        print_next();

        return;
    }
    b->preleft = b->left;
    b->pretop = b->top;
    b->top++;
    print_shape();
}

void move_left()
{
    if (b->left <= b->startY + 2) return;

    b->preleft = b->left;
    b->pretop = b->top;
    b->left -= 2;
    print_shape();
}

void move_right()
{
    if (b->left + (3 - b->shape[17]) * 2 - b->startY >= YLEN) return;
    b->preleft = b->left;
    b->pretop = b->top;
    b->left += 2;
    print_shape();
}

void key_control()
{
    char ch;
    struct termios old_term;
    struct termios cur_term;
    tcgetattr(STDIN_FILENO, &old_term);
    memcpy(&cur_term, &old_term, sizeof(cur_term));
    cur_term.c_lflag &= ~(ICANON|ECHO|ECHOE|ECHOK|ECHONL);
    cur_term.c_cc[VMIN] = 1;
    cur_term.c_cc[VTIME] = 0;
    tcsetattr(STDIN_FILENO, TCSAFLUSH, &cur_term);

    if (setjmp(b->env) == 0)
    {
        while (1)
        {
            ch = getchar();
            if (ch == ' ')
            {
                change_shape();
                continue;
            }
            if (ch == '\33')
            {
                ch = getchar();
                if (ch == '[')
                {
                    ch = getchar();
                    switch (ch)
                    {
                        case 'A':
                            change_shape();
                            break;
                        case 'B':
                            move_down();
                            break;
                        case 'C':
                            move_right();
                            break;
                        case 'D':
                            move_left();
                            break;
                    }
                }
            }
            if (ch == 'q' || ch == 'Q')
            {
                break;
            }
        }
        MOVETO(b->startX + YLEN + 2, b->startY);
        printf("\33[31m-------- gave interrupt exit! --------\33[0m\n");
    }
    tcsetattr(STDIN_FILENO, TCSAFLUSH, &old_term);
}

void change_shape()
{
    if (b->eraseflag == 0)
    {
        b->preleft = b->left;
        b->pretop = b->top;
        erase_preshape();
    }
    b->eraseflag = 1;
    b->mode = (b->mode + 1) % 4;
    b->shape = shape[b->num][b->mode];
    print_shape();
}

int main(int argc, char *argv[])
{
    block_init();
    print_UI();
    setitimer(ITIMER_REAL, &b->speed, NULL);
    signal(SIGALRM, move_down);
    key_control();
    block_destroy();

    return 0;
}

void gave_over()
{
    MOVETO(b->startX + YLEN + 2, b->startY);
    printf("\33[31m-------------  gave over!  ------------\33[0m\n");
    longjmp(b->env, 2);
}
